# -*-coding:Utf-8 -*
""" XAK Parsing module
Contains a basic XAK parser.
Part of Core module.
"""
import os,threading, re

RE_INTEGER = re.compile(r"([0-9]+)")
RE_FLOAT = re.compile(r"([0-9]*(?:\.)|,[0-9]*)")
RE_BOOL = re.compile(r"((?:[Ff]alse)|(?:[Tt]rue)|(?:[Oo]ui)|(?:[Nn]on?)|(?:[Yy]es)|[tToOyYfFnN])")
RE_NONE = re.compile(r"((?:[Nn]one)|(?:[Nn]ull))")
RE_TEXT = re.compile("[^\n\t ]")

def convert_bool(string):
	if string in ['t','T','true','True','o','O','oui','Oui','y','Y','yes','Yes']:
		return True
	return False

class Parser(object):

	def __init__(self):
		self._parser_markup_map = {}

	def add(self, tagName, func):
		if not callable(func):
			return
		if not tagName in self._parser_markup_map:
			self._parser_markup_map[tagName] = []
		self._parser_markup_map[tagName].append(func)

	def parse(self, *nodes):
		results = []
		for aNode in nodes:
			res = self.parse(aNode.nodeChilds)
			if aNode.tagName in self._parser_markup_map:
				for aFunc in self._parser_markup_map[aNode.tagName]:
					try:
						tmp = aFunc(aNode,res)
					except:
						pass
					else:
						res = tmp
			results.append(res)
		return results

class xakChildsList(object):

	def __init__(self, parent):
		self._list = []
		self.parent = parent
		super().__init__()

	def __len__(self):
		return len(self._list)
	def __contains__(self, child):
		if type(child) == str:
			for achild in self._list:
				if type(achild) == xakNode and achild.tagName:
					return True
		return False


	def __getitem__(self, key):
		key = int(key)
		if key < len(self._list):
			return self._list[key]
		else:
			raise IndexError("Child index out of range")
	def __delitem__(self, key):
		key = int(key)
		if key < len(self._list):
			self._list.remove(key)
		else:
			raise IndexError("Child index out of range")
	def __iter__(self):
		safe_list = list(self._list)
		for anElement in safe_list:
			yield anElement

	def _clean(self):
		for anElement in self._list:
			if type(anElement) == xakNode:
				anElement._clean()
		self.parent = None
		self._list = None

	def add(self, *childs, warning=True):
		for aChild in childs:
			if type(aChild) == str:
				self._list.append(aChild)
			elif type(aChild) == xakNode and aChild.document == self.parent.document and not aChild in self._list:
				self._list.append(aChild)
			elif warning:
				print("child "+str(aChild)+" not added")

	def nodeOnly(self):
		return [e for e in self._list if type(e) == xakNode]
		
	def textOnly(self):
		return  [e for e in self._list if type(e) == str]
		
	def convertToDict(self):
		ret = {0:''}
		for e in self._list:
			if type(e) == xakNode:
				if not e.tagName in ret:
					ret[e.tagName] = []
				ret[e.tagName].append(e.convertToDict())
			else:
				ret[0] += e
		return ret
		
	

class xakAttribute(object):

	def __init__(self, parent, name):
		self.parent = parent
		self._name = name
		super().__init__()

	def _clean(self):
		self.parent = None
		self._name = None

	@property
	def name(self):
		return self._name
	@name.setter
	def name(self, value):
		if not value in self.parent._attributes.keys():
			self.parent._attributes[value] = self.parent._attributes[self._name]
			del self.parent._attributes[self._name]
			self._name = value
		else:
			raise IndexError("Attribute "+str(value)+" exist")
	@property
	def value(self):
		if self._name in self.parent._attributes.keys():
			return self.parent._attributes[self._name]
		else:
			raise IndexError("Attribute "+self._name+" has been removed")

	@value.setter
	def value(self, value):
		if self._name in self.parent._attributes.keys():
			self.parent._attributes[self._name] = value
		else:
			raise IndexError("Attribute "+self._name+" has been removed")

class xakNode(object):

	TAG_NAME = 0
	ID_NAME = 'id'
	NAME_ATTR = 'name'

	def __init__(self, document,tagName, attributes = {}):
		self.document = document
		self._attributes = {}
		if type(attributes) == dict:
			self._attributes = attributes
		self._attributes[0] = tagName
		self.childs = xakChildsList(self)
		super().__init__()

	def __len__(self):
		return len(self._attributes)
	def __contains__(self, item):
		if item in self._attributes.keys():
			return True
		else:
			return False
	def __getitem__(self, key):
		if key in self._attributes.keys() and type(key) == str:
			return xakAttribute(self,key)
		else:
			raise IndexError("Attribute "+str(key)+" not found")
	def __setitem__(self, key, value):
		if type(key) == str:
			self._attributes[key] = value
		else:
			raise IndexError(" Attribute name must be a string ")
	def __delitem__(self, key):
		if key in self._attributes.keys() and type(key) == str:
			del self._attributes[key]
		else:
			raise IndexError("Attribute "+str(key)+" not found")
	def __iter__(self):
		safe_attr = dict(self._list)
		for aKey in safe_attr:
			if type(aKey) == str:
				yield xakAttribute(self,aKey)

	def _clean(self):
		self.childs._clean()
		self.childs = None
		self._attributes = None
		self.document = None

	@property
	def tagName(self):
		return self._attributes[0]
	@property
	def nodeChilds(self):
		return self.childs.nodeOnly()

	def getAttr(self, *attr, default=None):
		for anAttr in attr:
			if anAttr in self._attributes:
				return self._attributes[anAttr]
		return default

	def getAttributsList(self, *attr, default=None):
		results = []
		for anAttr in attr:
			if anAttr in self._attributes:
				results.append(self._attributes[anAttr])
			else:
				results.append(default)
		return results


	def child(self, name, allowList = False, nameToUseForChilds = TAG_NAME,default=None):
		results = []
		for a in self.nodeChilds:
			if nameToUseForChilds in a._attributes and a._attributes[nameToUseForChilds] == name:
				if not allowList:
					return a
				results.append(a)
		if results == []:
			return default
		return results

	def getAttributeOrChild(self, name, allowList = False,nameToUseForChilds = TAG_NAME,default=None):
		results = []
		if name in self._attributes.keys():
			if not allowList:
				return self[name]
			results.append(self[name])
		ret = self.child(name, allowList, nameToUseForChilds, default)
		if allowList and ret != default:
			return results+ret
		elif results == [] and ret == default:
			return default
		return ret
		
	def convertToDict(self):
		ret = {}
		ret.update(self._attributes)
		ret[1] = self.childs.convertToDict()
		return ret

class xakDocument(object):

	specialTagsNameChar = ('',' ','>')
	specialCloseTagsChar = ('','<','>')

	def __init__(self, document):
		self._error = False
		self._structure = None
		if type(document) == str:
			self._filename = document
		else:
			self._error = True
		super().__init__()

	@property
	def invalid(self):
		if self._error:
			return True
		return False
	def getError(self):
		if self._error:
			return self._error

	def _clean(self):
		if self._structure:
			self._structure._clean()
			self._structure = None

	def attrBuilding(self, attrDict, name, document):
		char = document.read(1)
		if char in (' ','\n','\t','>'):
			if name[0] == '!':
				attrDict[name[1:]] = False
			else:
				attrDict[name] = True
		elif char == '=':
			char = document.read(1)
			while char == ' ':
				char = document.read(1)
			resultStr = ''
			if char in ('"',"'"):
				openChar = char
				while char != '':
					char = document.read(1)
					if char == openChar and (resultStr == '' or ( resultStr and resultStr[-1] != '\\')):
						break
					elif char == openChar and resultStr and resultStr[-1] == '\\':
						resultStr = resultStr[:-1]+openChar
					else:
						if char == '&':
							cur = theFile.tell()
							string = char+document.read(5)
							if len(string)>=4 and string[:4] == '&lt;':
								document.seek(document.tell()-2)
								char = '<'
							elif len(string)>=4 and string[:4] == '&gt;':
								document.seek(document.tell()-2)
								char = '>'
							elif len(string)>=5 and string[:5] == '&amp;':
								document.seek(document.tell()-1)
								char = '&'
							elif len(string)==6 and string == '&apos;':
								char = "'"
							elif len(string)==6 and string == '&quot;':
								char = '"'
							else:
								document.seek(cur)
						resultStr = resultStr+char
				attrDict[name] = resultStr
			else:
				while not char in (' ', '/', '>') :
					resultStr = resultStr+char
					char = document.read(1)
				document.seek(document.tell()-1)
				if RE_FLOAT.match(resultStr):
					attrDict[name] = float(resultStr)
				elif RE_INTEGER.match(resultStr):
					attrDict[name] = int(resultStr)
				elif RE_BOOL.match(resultStr):
					attrDict[name] = convert_bool(resultStr)
				elif RE_NONE.match(resultStr) or resultStr == '':
					attrDict[name] = None



	def nodeBuilding(self, document):
		cursorSafety = document.tell()

		char = document.read(1)
		tagName = ""

		if char == '/':
			while not char in xakDocument.specialCloseTagsChar:
				char = document.read(1)
			if char == '':
				document.seek(cursorSafety+1)
			elif char == '<':
				document.seek(document.tell()-1)
			return None
		elif char == '!':
			if document.read(2) == '--':
				while char != '':
					char = document.read(1)
					if char == '-':
						if document.read(2) == '->':
							break
						document.seek(document.tell()-2)
				return None
			document.seek(document.tell()-2)

		while not char in xakDocument.specialTagsNameChar:
			tagName = tagName+char
			char = document.read(1)

		if char == '':
			self._error = "Tag not closed"
			return None

		attr = {}
		tagType = 1
		if char != '>':
			attrString = ''
			while char != '':
				if char in xakDocument.specialCloseTagsChar:
					if attrString and attrString[-1] == '/':
						tagType = 0
						attrString = attrString[:-1]
					if attrString:
						document.seek(document.tell()-1)
						self.attrBuilding(attr, attrString, document)
					break
				elif char == '=':
					document.seek(document.tell()-1)
					self.attrBuilding(attr, attrString, document)
					attrString = ''
				elif char in (' ','\n','\t'):
					if document.read(1) != "=" and attrString:
						document.seek(document.tell()-2)
						self.attrBuilding(attr, attrString, document)
						attrString = ''
					document.seek(document.tell()-1)
				else:
					attrString = attrString+char
				char = document.read(1)
			if char == '':
				self._error = "End of tag \""+tagName+"\" not find."
				tagType = 0
		elif tagName[-1] == '/':
			tagType = 0
			tagName = tagName[:-1]

		node = xakNode(self,tagName, attr)
		childs = {}
		cursorSafety = document.tell()
		if tagType:
			text = ''
			while char != '':
				char = document.read(1)
				if char == "<":
					cur = document.tell()
					#print('lecture de la balise, etat du text 1: '+str(text))
					if document.read(1+len(tagName)) == '/'+tagName:
						while text and text[-1] in ('\n','\t',' '):
							text = text[:-1]
						text = text.strip()
						if text:
							node.childs.add(text)
						while char != '>':
								char = document.read(1)
								if char == '<':
										document.seek(document.tell()-1)
										break
								elif char == '':
										break
						break
					else:
						#print('lecture de la balise, etat du text 2: '+str(text))
						document.seek(cur)
						childNode = self.nodeBuilding(document)
						if childNode != None:
							while text and text[-1] in ('\n','\t',' '):
								text = text[:-1]
							text = text.strip()
							if text:
								node.childs.add(text)
							node.childs.add(childNode)
							text = ''
				else:
					if char == '&':
						cur = document.tell()
						string = char+document.read(5)
						if len(string)>=4 and string[:4] == '&lt;':
							document.seek(document.tell()-2)
							char = '<'
						elif len(string)>=4 and string[:4] == '&gt;':
							document.seek(document.tell()-2)
							char = '>'
						elif len(string)>=5 and string[:5] == '&amp;':
							document.seek(document.tell()-1)
							char = '&'
						elif len(string)==6 and string == '&apos;':
							char = "'"
						elif len(string)==6 and string == '&quot;':
							char = '"'
						else:
							document.seek(cur)
					if char in ('\n','\t',' ') and RE_TEXT.search(text):
						if char == '\n':
							text = text+char
						elif char == '\t' and text[-1] == '\n':
							text = text+' '
						elif text[-1] not in (' ', '\t'):
							text = text+char
					elif not char in ('\n','\t',' '):
						text = text+char
			cursorSafety = document.tell()
		document.seek(cursorSafety)
		return node
	"""def passNode(self, document):
		cursorSafety = document.tell()

		char = document.read(1)
		tagName = ""

		if char == '/':
			while not char in xakDocument.specialCloseTagsChar:
				char = document.read(1)
			if char == '':
				document.seek(cursorSafety+1)
			elif char == '<':
				document.seek(document.tell()-1)
			return None
		elif char == '!':
			if document.read(2) == '--':
				while char != '':
					char = document.read(1)
					if char == '-' and document.read(2) == '->':
						break
					document.seek(document.tell()-2)
				return None
			document.seek(document.tell()-2)

		while not char in xakDocument.specialTagsNameChar:
			tagName = tagName+char
			char = document.read(1)
		while not char in xakDocument.specialCloseTagsChar:
			new = document.read(1)
			if new == '"' or new == "'" and char != '\\':
				newnew = new
				while new != '':
					char = document.read(1)
					if char == newnew and new != '\\':
						break
					new = char
			else:
				char = new
		if char == '':
			self._error = "Tag not closed"
			return None"""

	def build(self, childToLoad = None):
		if self.invalid:
			return None
		document = os.path.normpath(self._filename)
		theFile = None
		try:
			theFile = open(document,"r")
		except:
			self._error = "Open File Error"
			return "Je peux pas ouvrir ton putain de fichier"

		char = theFile.read(1)
		while char != "":
			if char == "<" and theFile.read(3) == "xak":
				while char != ">":
					char = theFile.read(1)
					if char == "":
						break
				break
			char = theFile.read(1)
		if char == "":
			theFile.close()
			self._error = "Root tag not found"
			return "j'ai rien trouvé"
		structure = xakNode(self,"xak")
		self._error = "Root close tag not found"
		while char != "":
			if char == "<":
				cur = theFile.tell()
				if theFile.read(4) == "/xak":
					self._error = False
					break
				else:
					if childToLoad and type(childToLoad) == str:
						CtoR = len(childToLoad)

					theFile.seek(cur)
					structure.childs.add(self.nodeBuilding(theFile),warning=False)
			char = theFile.read(1)

		theFile.close()
		self._structure = structure
		return

	def getHead(self):
		if self._structure == None:
			self.build()
		if self.invalid:
			return None
		return self._structure.child("xaks")


	def getBody(self):
		if self._structure == None:
			self.build()
		if self.invalid:
			return None
		return self._structure.child("xakp")








